package org.vison.wonfu.framework.dao.spi;

import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.vison.wonfu.framework.dao.Column;
import org.vison.wonfu.framework.dao.TableDefine;

import java.io.File;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Maven插件，用于生成MySQL DDL语句并直接输出到终端
 * 对应模块编译后，会在target/classes目录下生成对应的class文件，才能执行
 */
@Mojo(name = "generate-ddl", defaultPhase = LifecyclePhase.COMPILE, requiresDependencyResolution = org.apache.maven.plugins.annotations.ResolutionScope.TEST)
public class DDLGeneratorMojo extends AbstractMojo {
    
    /**
     * 当前Maven项目
     */
    @Parameter(defaultValue = "${project}", required = false, readonly = true)
    private MavenProject project;

    /**
     * 指定要生成DDL的PO类全限定名
     */
    @Parameter(property = "poClass", required = true)
    private String poClass;


    @Override
    public void execute() throws MojoExecutionException {
        if(poClass==null || poClass.isEmpty()){
            getLog().info("PO class name is empty, please set it by -DpoClass=packageName.ClassName");
            return;
        }
        getLog().info("Starting MySQL DDL generation...");
        getLog().info("Attempting to load PO class: " + poClass);
        
        try {
            // 获取项目的编译类路径和测试类路径
            List<String> classpathElements = new ArrayList<>();
            
            // 添加编译类路径
            try {
                classpathElements.addAll(project.getCompileClasspathElements());
            } catch (DependencyResolutionRequiredException e) {
                getLog().warn("Cannot resolve compile classpath: " + e.getMessage());
            }
            
            // 添加测试类路径
            try {
                classpathElements.addAll(project.getTestClasspathElements());
            } catch (DependencyResolutionRequiredException e) {
                getLog().warn("Cannot resolve test classpath: " + e.getMessage());
            }
            
            // 确保添加target/classes目录（当前项目的输出目录）
            File classesDir = new File(project.getBuild().getOutputDirectory());
            if (classesDir.exists() && !classpathElements.contains(classesDir.getAbsolutePath())) {
                classpathElements.add(classesDir.getAbsolutePath());
            }
            
            // 确保添加target/test-classes目录（测试类输出目录）
            File testClassesDir = new File(project.getBuild().getTestOutputDirectory());
            if (testClassesDir.exists() && !classpathElements.contains(testClassesDir.getAbsolutePath())) {
                classpathElements.add(testClassesDir.getAbsolutePath());
            }
            
            // 记录类路径用于调试
            getLog().debug("Classpath elements: ");
            for (String element : classpathElements) {
                getLog().debug("  " + element);
            }
            
            // 创建URL数组
            URL[] urls = new URL[classpathElements.size()];
            for (int i = 0; i < classpathElements.size(); i++) {
                try {
                    urls[i] = new File(classpathElements.get(i)).toURI().toURL();
                    getLog().debug("Added URL: " + urls[i]);
                } catch (MalformedURLException e) {
                    getLog().warn("Invalid classpath element: " + classpathElements.get(i), e);
                }
            }
            
            // 创建URLClassLoader来加载所有依赖的类
            URLClassLoader classLoader = new URLClassLoader(urls, getClass().getClassLoader());
            
            // 使用自定义ClassLoader加载PO类
            Class<?> clazz = classLoader.loadClass(poClass);
            getLog().info("Successfully loaded class: " + clazz.getName());
            getLog().info("Class loaded from: " + clazz.getProtectionDomain().getCodeSource().getLocation());

            // 验证PO类定义
            List<String> errorMessages = new ArrayList<>();
            EntityDefineChecker.validateIPOClass(clazz, errorMessages);
            if (!errorMessages.isEmpty()) {
                getLog().debug("PO class validation errors: " + String.join("\n", errorMessages));
            }
            // 生成DDL
            String ddl = generateTableDDL(clazz);
            
            // 直接输出到终端
            System.out.println("\n==============================================");
            System.out.println("Generated MySQL DDL for table:");
            System.out.println("==============================================");
            System.out.println(ddl);
            System.out.println("==============================================\n");
            
            getLog().info("DDL generation completed successfully.");
            
        } catch (ClassNotFoundException e) {
            getLog().error("Class not found: " + poClass + ". Please check if the class exists and is compiled.");
            getLog().error("Current project: " + project.getArtifactId() + ", Group: " + project.getGroupId());
            throw new MojoExecutionException("Cannot find PO class: " + poClass + ". Ensure the class is compiled and in the classpath.", e);
        } catch (Exception e) {
            getLog().error("Error during DDL generation", e);
            throw new MojoExecutionException("Error generating DDL", e);
        }
    }
    
    private String generateTableDDL(Class<?> clazz) {
        StringBuilder ddl = new StringBuilder();
        
        // 获取表名
        String tableName = getTableName(clazz);
        
        // 表注释
        String tableComment = getTableComment(clazz);
        
        ddl.append("CREATE TABLE `").append(tableName).append("` (");
        
        // 获取所有字段
        Field[] fields = clazz.getDeclaredFields();
        boolean first = true;

        // 存储唯一索引字段
        List<String> uniqueFields = new ArrayList<>();

        for (Field field : fields) {
            if (field.isAnnotationPresent(Column.class)) {
                if (!first) {
                    ddl.append(",\n  ");
                } else {
                    ddl.append("\n  ");
                    first = false;
                }
                
                Column column = field.getAnnotation(Column.class);
                String fieldName = field.getName();
                // 转下划线命名
                fieldName = fieldName.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
                String columnName = column.name().isEmpty() ? fieldName : column.name();
                String columnType = getColumnType(field.getType(), column);
                
                ddl.append("`").append(columnName).append("` ").append(columnType);
                
                // 处理NOT NULL
                if (!column.nullable()) {
                    ddl.append(" NOT NULL");
                }
                
                // 处理默认值
                if (!column.defaultValue().isEmpty()) {
                    ddl.append(" DEFAULT ").append(column.defaultValue());
                }
                
                // 处理时间字段默认值
                if (column.timeDefault() != Column.TimeDefault.NONE) {
                    ddl.append(" DEFAULT ");
                    if (column.timeDefault() == Column.TimeDefault.CURRENT_TIMESTAMP) {
                        ddl.append("CURRENT_TIMESTAMP");
                    } else if (column.timeDefault() == Column.TimeDefault.CURRENT_TIMESTAMP_ON_UPDATE) {
                        ddl.append("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP");
                    }
                }
                
                // 处理自增
                if (column.autoIncrement()) {
                    ddl.append(" AUTO_INCREMENT");
                }
                
                // 处理注释
                ddl.append(" COMMENT '").append(column.comment()).append("'");

                // 处理唯一索引
                if (column.unique()) {
                    uniqueFields.add(columnName);
                }
            }
        }
        
        // 处理主键
        Field[] pkFields = getPrimaryKeyFields(clazz);
        if (pkFields.length > 0) {
            ddl.append(",\n  PRIMARY KEY (");
            boolean pkFirst = true;
            for (Field pkField : pkFields) {
                if (!pkFirst) {
                    ddl.append(", ");
                }
                Column column = pkField.getAnnotation(Column.class);
                String columnName = column.name().isEmpty() ? pkField.getName() : column.name();
                ddl.append("`").append(columnName).append("`");
                pkFirst = false;
            }
            ddl.append(")");
        }
        // 处理唯一索引
        for (String uniqueField : uniqueFields) {
            ddl.append(",\n  UNIQUE KEY `").append("uk_").append(uniqueField).append("` (").append("`").append(uniqueField).append("`)");
        }

        
        // 结束DDL
        ddl.append("\n) ENGINE=InnoDB");
        
        // 添加表注释
        if (!tableComment.isEmpty()) {
            ddl.append(" COMMENT='").append(tableComment).append("'");
        }
        
        ddl.append(";\n");
        
        return ddl.toString();
    }
    
    private String getTableName(Class<?> clazz) {
        // 优先使用TableName注解
        com.baomidou.mybatisplus.annotation.TableName tableNameAnnotation = clazz.getAnnotation(com.baomidou.mybatisplus.annotation.TableName.class);
        if (tableNameAnnotation != null && !tableNameAnnotation.value().isEmpty()) {
            return tableNameAnnotation.value();
        }
        
        // 使用类名转换（驼峰转下划线）
        String className = clazz.getSimpleName();
        return className.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
    }
    
    private String getTableComment(Class<?> clazz) {
        TableDefine tableDefine = clazz.getAnnotation(TableDefine.class);
        if (tableDefine != null) {
            return tableDefine.comment();
        }
        return "";
    }
    
    private String getColumnType(Class<?> fieldType, Column column) {
        // 如果注解中指定了type，则使用指定的type
        if (column.type() != Column.ColumnType.NONE && column.type() != Column.ColumnType.DECIMAL) {
            return column.type().name();
        } else if (column.type().equals(Column.ColumnType.DECIMAL)) {
            if (column.precision() > 0 && column.scale() >= 0) {
                return "DECIMAL(" + column.precision() + "," + column.scale() + ")";
            }
        }
        // 根据Java类型映射为MySQL类型
        if (String.class.isAssignableFrom(fieldType)) {
            if (column.length() > 0) {
                return "VARCHAR(" + column.length() + ")";
            }
            return "VARCHAR(255)";
        } else if (Integer.class.isAssignableFrom(fieldType) || int.class == fieldType) {
            return "INT";
        } else if (Long.class.isAssignableFrom(fieldType) || long.class == fieldType) {
            return "BIGINT";
        } else if (Boolean.class.isAssignableFrom(fieldType) || boolean.class == fieldType) {
            return "TINYINT(1)";
        } else if (Double.class.isAssignableFrom(fieldType) || double.class == fieldType) {
            return "DOUBLE";
        } else if (java.time.LocalDateTime.class.isAssignableFrom(fieldType)) {
            return "DATETIME";
        } else if (java.time.LocalDate.class.isAssignableFrom(fieldType)) {
            return "DATE";
        } else if (byte[].class.isAssignableFrom(fieldType)) {
            return "BLOB";
        }
        
        // 默认返回VARCHAR
        return "VARCHAR(255)";
    }
    
    private Field[] getPrimaryKeyFields(Class<?> clazz) {
        Set<Field> pkFields = new HashSet<>();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Column.class)) {
                Column column = field.getAnnotation(Column.class);
                if (column.primaryKey()) {
                    pkFields.add(field);
                }
            }
        }
        return pkFields.toArray(new Field[0]);
    }
}